#include <algorithm>
#include <iostream>
#include <list>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include "pings.h"

#include "../tinyxml/lossxml.h"

using namespace std;

const int secPerRound = 10; // seconds per round
const int packetPerSec = 5; // how many packets we send per second
const int expectRecCnt = secPerRound * packetPerSec;

pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER;
//pthread_mutex_t anylize_mutex = PTHREAD_MUTEX_INITIALIZER;

struct Statistics
{
	string peerAddr; // IP address
	int recvCount; // how many packets received in this round
	double minDelay; // exclude max/min delay value, then calculate the average value
	double maxDelay;
	double totalDelay;

public:
	bool operator ==(string a) const
	{
		return (peerAddr == a);
	}

};

list<Statistics> stList; // save information for each peer

void addPeer(struct Statistics *);

void *msgListen(void *);
void *sndIcmp(void *);
void *revIcmp4(void *);
void *revIcmp6(void *);
void *anylize(void *);

struct proto proto_v4 = { sockfd4, proc_v4, send_v4, NULL, NULL, 0,
		IPPROTO_ICMP };
struct proto proto_v6 = { sockfd6, proc_v6, send_v6, NULL, NULL, 0,
		IPPROTO_ICMPV6 };

MsgQue msgQue("/", 0);

int main()
{
	pr4 = &proto_v4;
	pr6 = &proto_v6;


	int size = 60 * 1024;
	sockfd4 = socket(PF_INET, SOCK_RAW, pr4->icmpproto);
	sockfd6 = socket(PF_INET6, SOCK_RAW, pr6->icmpproto);
	setuid(getuid()); // don't need special permission any more
	setsockopt(sockfd4, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); //set socket recv buff size
	setsockopt(sockfd6, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); //set socket recv buff size
	// sig_alrm(SIGALRM); // send first packet
	//

	list<Statistics>::iterator it;
	struct Statistics stAddr1, stAddr2, stAddr3;

	stAddr1.peerAddr = "59.64.159.49";
	addPeer(&stAddr1);
	//stList.push_back(stAddr1);
	stAddr2.peerAddr = "2001:da8:215:3592:218:8bff:fee5:4022";
	addPeer(&stAddr2);
	//stList.push_back(stAddr2);
	stAddr3.peerAddr = "::1";
	addPeer(&stAddr3);
	//stList.push_back(stAddr3);

	resetStatistics();

	pthread_t tidSnd, tidRec4, tidRec6, tidAnlz; // tidMsg, for receiving measurement request, future implement
	//	pthread_create(&tidMsg, NULL, &msgListen, NULL);	 // to receive measurement requests from msgqueue in the future
	pthread_create(&tidSnd, NULL, &sndIcmp, NULL);
	pthread_create(&tidRec4, NULL, &revIcmp4, NULL);
	pthread_create(&tidRec6, NULL, &revIcmp6, NULL);

	usleep(secPerRound * 1000000 - 200000);
	pthread_create(&tidAnlz, NULL, &anylize, NULL);

	//pthread_join(tidMsg, NULL);
	pthread_join(tidSnd, NULL);
	pthread_join(tidRec4, NULL);
	pthread_join(tidRec6, NULL);
	pthread_join(tidAnlz, NULL);

	return 0;
}

//void *msgListen(void *vptr)
//{
//	while (1)
//	{
//		sleep(5);
//		//		printf("msgListen\n");
//
//	}
//	return NULL;
//}

void *sndIcmp(void *vptr)
{
	list<Statistics>::iterator it;
	while (1)
	{
		//pthread_mutex_lock(&list_mutex); // read list, mutex
		int listLen = stList.size();
		struct addrinfo *ai;
		string peerAddr[listLen];

		it = stList.begin();
		for (int i = 0; i < listLen; i++)
		{
			peerAddr[i] = it->peerAddr;
			it++;
		}
		//pthread_mutex_unlock(&list_mutex);

		for (int i = 0; i < listLen; i++)
		{

			ai = host_serv(peerAddr[i].c_str(), NULL, 0, 0);
			printf("PING %s: %d data bytes\n", ai->ai_canonname, datalen);

			if (ai->ai_family == AF_INET)
			{
				pr4->sasend = ai->ai_addr;
				pr4->sarecv = (sockaddr *) calloc(1, ai->ai_addrlen);
				pr4->salen = ai->ai_addrlen;
				send_v4(i);
			} else if (ai->ai_family == AF_INET6)
			{
				pr6->sasend = ai->ai_addr;
				pr6->sarecv = (sockaddr *) calloc(1, ai->ai_addrlen);
				pr6->salen = ai->ai_addrlen;
				if (IN6_IS_ADDR_V4MAPPED(&(((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr)))
				{
					printf("cann't ping IPv4-mapped IPv6 address\n");
					continue;//exit(1);
				}
				send_v6(i);
			} else
			{
				printf("unknown address family\n");
				exit(1);
			}
		}

		sleep(1);
	}
	return NULL;
}

void *revIcmp4(void *vptr)
{
	socklen_t len;
	ssize_t n;
	struct timeval tval;

	for (;;)
	{
		len = pr4->salen;
		bzero(recvbuf4, sizeof(recvbuf4));
		//if 127.0.0.1 or ::1, recvfrom will catch the first ICMP_ECHO_REQUEST !!!
		n = recvfrom(sockfd4, recvbuf4, sizeof(recvbuf4), 0, pr4->sarecv, &len);
		if (n < 0)
		{
			if (errno == EINTR)
				continue;
			else
			{
				printf("recvfrom error\n");
				continue; // err_sys
			}
		}
		gettimeofday(&tval, NULL);

		(*pr4->fproc)(recvbuf4, n, &tval);
	}

	return NULL;
}

void *revIcmp6(void *vptr)
{
	socklen_t len;
	ssize_t n;
	struct timeval tval;

	for (;;)
	{
		len = pr6->salen;
		bzero(recvbuf6, sizeof(recvbuf6));
		//if 127.0.0.1 or ::1, recvfrom will catch the first ICMP_ECHO_REQUEST !!!
		n = recvfrom(sockfd6, recvbuf6, sizeof(recvbuf6), 0, pr6->sarecv, &len);
		if (n < 0)
		{
			if (errno == EINTR)
				continue;
			else
			{
				printf("recvfrom error\n");
				continue; // err_sys
			}
		}
		gettimeofday(&tval, NULL);
		(*pr6->fproc)(recvbuf6, n, &tval);
	}

	return NULL;
}

void *anylize(void *vptr)
{
	list<Statistics>::iterator it;
	char msg[MBUFSIZ];

	time_t curTime;


	LOSS_XML xml;
	xml.setTitle("ContextRequest");
	xml.setType("linkState");
	xml.setOper("store");
	xml.setNID(12345678);

	while (1)
	{
		//pthread_mutex_lock(&list_mutex);
		/*
		 * out put all the data
		 * for the future implement
		 */
		int listLen = stList.size();
		printf("listLen %d: \n", listLen);
		list<Statistics>::iterator it = stList.begin();
		for (int i = 0; i < listLen; i++)
		{
			sprintf(msg, "max: %.3f\tmin: %.3f\ttotal: %.3f\trecvCount: %d\tavg: %.3f\tlossrate:%.2f\n",
					it->maxDelay, it->minDelay, it->totalDelay, it->recvCount,
					(double) (it->totalDelay - it->maxDelay - it->minDelay) / (it->recvCount - 2),
					100.0 * (expectRecCnt - it->recvCount) / expectRecCnt);

			// XML forming~
			time(&curTime);
			xml.setTime(curTime);
			xml.setDesAddr("inet ipv9", (char *)(it->peerAddr).c_str());


			xml.formXML(msg);
			printf("%s", msg);
//			msgQue.send(1, msg);
			it++;
		}

		//pthread_mutex_unlock(&list_mutex);

		resetStatistics();
		sleep(10);
	}
	return NULL;
}

void tv_sub(struct timeval *out, struct timeval *in)
{
	if ((out->tv_usec -= in->tv_usec) < 0)
	{
		--out->tv_sec;
		out->tv_usec += 1000000;
	}
	out ->tv_sec -= in->tv_sec;
}

void proc_v4(char *ptr, ssize_t len, struct timeval *tvrecv)
{
	int hlen1, icmplen;
	int index;
	double rtt;
	struct ip *ip;
	struct icmp *icmp;
	struct timeval *tvsend;

	list<Statistics>::iterator it = stList.begin();

	ip = (struct ip *) ptr; // start of IP header
	hlen1 = ip ->ip_hl << 2; // length of IP header

	icmp = (struct icmp *) (ptr + hlen1); //start of ICMP header
	if ((icmplen = len - hlen1) < 8)
	{
		printf("icmp length < 8, exit...\n");
		exit(1);
	}

	//pthread_mutex_lock(&list_mutex);
	int listLen = stList.size();
	if (icmp->icmp_type == ICMP_ECHOREPLY)
	{
		index = icmp ->icmp_id - pid; // not a correct response
		//		printf("index: %d\t", index);
		if (index < 0 || index > listLen)
			return;

		if (icmplen < 16)
		{
			printf("icmp length < 16, exit...\n"); //redundant?
			exit(1);
		}
		tvsend = (struct timeval *) icmp->icmp_data;
		tv_sub(tvrecv, tvsend);
		rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0;

		for (int i = 0; i < index; i++)
			// go to the specified element in stList
			it++;

		it->recvCount++;
		it->totalDelay += rtt;
		if (rtt > (it->maxDelay))
			it->maxDelay = rtt;
		else if (rtt < (it->minDelay))
			it->minDelay = rtt;

		//		printf("%d bytes from %s: seq = %u, ttl = %d, rtt = %.3f ms, total = %.3f ms\n",
		//				icmplen, inet_ntoa(((sockaddr_in *) pr4->sarecv)->sin_addr),// inet_ntoa only works in IPv4; inet_ntop IPv6+IPv4
		//				icmp->icmp_seq, ip->ip_ttl, rtt, it->totalDelay);
	}
	//pthread_mutex_unlock(&list_mutex);


}

void proc_v6(char *ptr, ssize_t len, struct timeval *tvrecv)
{
	//#ifdef IPV6
	int icmp6len;
	int index;
	double rtt;
	//struct ip6_hdr *ip6;
	struct icmp6_hdr *icmp6;
	struct timeval *tvsend;

	icmp6 = (struct icmp6_hdr *) ptr; // (ptr + hlen1); //start of ICMP6 header
	if ((icmp6len = len) < 8) // - hlen1
	{
		printf("icmp6 length < 8, exit...\n");
		exit(1);
	}

	list<Statistics>::iterator it = stList.begin();
	int listLen = stList.size();

	if (icmp6->icmp6_type == ICMP6_ECHO_REPLY)
	{
		//		if (icmp6 ->icmp6_id != pid)
		//			return; // not a response to out ECHO_REQUEST
		index = icmp6 ->icmp6_id - pid; // not a correct response
		//		printf("index: %d\t", index);
		if (index < 0 || index > listLen)
			return;

		if (icmp6len < 16)
		{
			printf("icmp6 length < 16, exit...\n"); //redundant?
			exit(1);
		}
		tvsend = (struct timeval *) (icmp6 + 1);
		tv_sub(tvrecv, tvsend);
		rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0;

		for (int i = 0; i < index; i++)
			// go to the specified element in stList
			it++;

		it->recvCount++;
		it->totalDelay += rtt;
		if (rtt > (it->maxDelay))
			it->maxDelay = rtt;
		else if (rtt < (it->minDelay))
			it->minDelay = rtt;

		//		printf("%d bytes : seq = %u, hlim = %d, rtt = %.3f ms\n", icmp6len,
		//				icmp6->icmp6_seq, 64, rtt); //ip6->ip6_hlim is replaced by 64
	}

}

void send_v4(int index)
{
	int len;
	struct icmp *icmp;
	icmp = (struct icmp *) sendbuf;
	icmp->icmp_type = ICMP_ECHO;
	icmp->icmp_code = 0;
	icmp->icmp_id = pid + index;

	for (int i = 0; i < 5; i++) //  send 5 icmp_request at one time
	{
		icmp->icmp_seq = nsent++;
		gettimeofday((struct timeval *) icmp->icmp_data, NULL);

		len = 8 + datalen; //checksum ICMP header and data
		icmp->icmp_cksum = 0;
		icmp->icmp_cksum = in_cksum((u_short *) icmp, len);
		sendto(sockfd4, sendbuf, len, 0, pr4->sasend, pr4->salen);
	}
}

void send_v6(int index)
{
	int len;
	struct icmp6_hdr *icmp6;
	icmp6 = (struct icmp6_hdr *) sendbuf;
	icmp6->icmp6_type = ICMP6_ECHO_REQUEST;
	icmp6->icmp6_code = 0;
	icmp6->icmp6_id = pid + index;

	for (int i = 0; i < 5; i++) //  send 5 icmp_request at one time
	{
		icmp6->icmp6_seq = nsent++;
		gettimeofday((struct timeval *) (icmp6 + 1), NULL);

		len = 8 + datalen; //8 bytes ICMPv6 header
		sendto(sockfd6, sendbuf, len, 0, pr6->sasend, pr6->salen);
	}
	// kernel calculates and stores checksum
}

/*
 * add a peer to the stList for further measurement
 */
void addPeer(struct Statistics *a)
{
	list<Statistics>::iterator it;
	it = find(stList.begin(), stList.end(), a->peerAddr);
	if (it == stList.end()) // if not exits
	{
		stList.push_back(*a);
		cout << "Addr a peer: " << a->peerAddr << "\t total count: "
				<< stList.size() << endl;
	}
}

// reset all the statistics
void resetStatistics()
{
	pthread_mutex_lock(&list_mutex);
	int listLen = stList.size();
	list<Statistics>::iterator it = stList.begin();
	for (int i = 0; i < listLen; i++)
	{
		it->maxDelay = 0.0;
		it->minDelay = 1000.0;
		it->totalDelay = 0.0;
		it->recvCount = 0;
		it++;
	}
	pthread_mutex_unlock(&list_mutex);
}

/*
 * open source from Ping
 */
unsigned short in_cksum(unsigned short *addr, int len)
{
	int nleft = len;
	int sum = 0;
	unsigned short *w = addr;
	unsigned short answer = 0;

	while (nleft > 1)
	{
		sum += *w++;
		nleft -= 2;
	}

	if (nleft == 1)
	{
		*(unsigned char *) (&answer) = *(unsigned char *) w;
		sum += answer;
	}

	sum = (sum >> 16) + (sum & 0xffff); //add hi 16 to low 16
	sum += (sum >> 16);
	answer = ~sum;
	return (answer);
}

struct addrinfo *host_serv(const char *host, const char *serv, int family,
		int socktype)
{
	int n;
	struct addrinfo hints, *res;

	bzero(&hints, sizeof(struct addrinfo));
	hints.ai_flags = AI_CANONNAME; //always return canonical name
	hints.ai_family = family; //AF_UNSPEC, AF_INET, AF_INET6, etc
	hints.ai_socktype = socktype; //0, SOCK_STREAM, SOCK_DGRAM, etc

	if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
		return NULL;
	return (res);
}
